
#include "platform_device_win.h"

#include "SkMetaData.h"
#include "SkMatrix.h"
#include "SkPath.h"
#include "SkRegion.h"
#include "SkUtils.h"

#include "skia_utils_win.h"

namespace skia
{

    namespace
    {
        const char* kDevicePlatformBehaviour = "CrDevicePlatformBehaviour";
    }

    void SetPlatformDevice(SkDevice* device, PlatformDevice* platform_behaviour)
    {
        SkMetaData& meta_data = device->getMetaData();
        meta_data.setPtr(kDevicePlatformBehaviour, platform_behaviour);
    }

    PlatformDevice* GetPlatformDevice(SkDevice* device)
    {
        SkMetaData& meta_data = device->getMetaData();
        PlatformDevice* device_behaviour = NULL;
        if(meta_data.findPtr(kDevicePlatformBehaviour,
            reinterpret_cast<void**>(&device_behaviour)))
        {
            return device_behaviour;
        }

        return NULL;
    }

    void InitializeDC(HDC context)
    {
        // Enables world transformation.
        // If the GM_ADVANCED graphics mode is set, GDI always draws arcs in the
        // counterclockwise direction in logical space. This is equivalent to the
        // statement that, in the GM_ADVANCED graphics mode, both arc control points
        // and arcs themselves fully respect the device context's world-to-device
        // transformation.
        BOOL res = SetGraphicsMode(context, GM_ADVANCED);
        SkASSERT(res != 0);

        // Enables dithering.
        res = SetStretchBltMode(context, HALFTONE);
        SkASSERT(res != 0);
        // As per SetStretchBltMode() documentation, SetBrushOrgEx() must be called
        // right after.
        res = SetBrushOrgEx(context, 0, 0, NULL);
        SkASSERT(res != 0);

        // Sets up default orientation.
        res = SetArcDirection(context, AD_CLOCKWISE);
        SkASSERT(res != 0);

        // Sets up default colors.
        res = SetBkColor(context, RGB(255, 255, 255));
        SkASSERT(res != CLR_INVALID);
        res = SetTextColor(context, RGB(0, 0, 0));
        SkASSERT(res != CLR_INVALID);
        res = SetDCBrushColor(context, RGB(255, 255, 255));
        SkASSERT(res != CLR_INVALID);
        res = SetDCPenColor(context, RGB(0, 0, 0));
        SkASSERT(res != CLR_INVALID);

        // Sets up default transparency.
        res = SetBkMode(context, OPAQUE);
        SkASSERT(res != 0);
        res = SetROP2(context, R2_COPYPEN);
        SkASSERT(res != 0);
    }

    HDC PlatformDevice::BeginPlatformPaint()
    {
        return 0;
    }

    void PlatformDevice::EndPlatformPaint()
    {
        // We don't clear the DC here since it will be likely to be used again.
        // Flushing will be done in onAccessBitmap.
    }

    void PlatformDevice::DrawToNativeContext(HDC surface, int x, int y,
        const RECT* src_rect) {}

    // static
    bool PlatformDevice::LoadPathToDC(HDC context, const SkPath& path)
    {
        switch(path.getFillType())
        {
        case SkPath::kWinding_FillType:
            {
                int res = SetPolyFillMode(context, WINDING);
                SkASSERT(res != 0);
                break;
            }
        case SkPath::kEvenOdd_FillType:
            {
                int res = SetPolyFillMode(context, ALTERNATE);
                SkASSERT(res != 0);
                break;
            }
        default:
            {
                SkASSERT(false);
                break;
            }
        }
        BOOL res = BeginPath(context);
        SkASSERT(res != 0);
        if(!res)
        {
            return false;
        }

        CubicPaths paths;
        if(!SkPathToCubicPaths(&paths, path))
        {
            return false;
        }

        std::vector<POINT> points;
        for(CubicPaths::const_iterator path(paths.begin()); path!=paths.end(); ++path)
        {
            if(!path->size())
            {
                continue;
            }
            points.resize(0);
            points.reserve(path->size() * 3 / 4 + 1);
            points.push_back(SkPointToPOINT(path->front().p[0]));
            for(CubicPath::const_iterator point(path->begin());
                point!=path->end(); ++point)
            {
                // Never add point->p[0]
                points.push_back(SkPointToPOINT(point->p[1]));
                points.push_back(SkPointToPOINT(point->p[2]));
                points.push_back(SkPointToPOINT(point->p[3]));
            }
            SkASSERT((points.size()-1) % 3 == 0);
            // This is slightly inefficient since all straight line and quadratic lines
            // are "upgraded" to a cubic line.
            // TODO(maruel):  http://b/1147346 We should use
            // PolyDraw/PolyBezier/Polyline whenever possible.
            res = PolyBezier(context, &points.front(),
                static_cast<DWORD>(points.size()));
            SkASSERT(res != 0);
            if(res == 0)
            {
                break;
            }
        }
        if(res == 0)
        {
            // Make sure the path is discarded.
            AbortPath(context);
        }
        else
        {
            res = EndPath(context);
            SkASSERT(res != 0);
        }
        return true;
    }

    // static
    void PlatformDevice::LoadTransformToDC(HDC dc, const SkMatrix& matrix)
    {
        XFORM xf;
        xf.eM11 = matrix[SkMatrix::kMScaleX];
        xf.eM21 = matrix[SkMatrix::kMSkewX];
        xf.eDx = matrix[SkMatrix::kMTransX];
        xf.eM12 = matrix[SkMatrix::kMSkewY];
        xf.eM22 = matrix[SkMatrix::kMScaleY];
        xf.eDy = matrix[SkMatrix::kMTransY];
        SetWorldTransform(dc, &xf);
    }

    // static
    bool PlatformDevice::SkPathToCubicPaths(CubicPaths* paths,
        const SkPath& skpath)
    {
        paths->clear();
        CubicPath* current_path = NULL;
        SkPoint current_points[4];
        CubicPoints points_to_add;
        SkPath::Iter iter(skpath, false);
        for(SkPath::Verb verb=iter.next(current_points);
            verb!=SkPath::kDone_Verb;
            verb=iter.next(current_points))
        {
            switch(verb)
            {
            case SkPath::kMove_Verb:
                {
                    // iter.next returns 1 point
                    // Ignores it since the point is copied in the next operation. See
                    // SkPath::Iter::next() for reference.
                    paths->push_back(CubicPath());
                    current_path = &paths->back();
                    // Skip point addition.
                    continue;
                }
            case SkPath::kLine_Verb:
                {
                    // iter.next returns 2 points
                    points_to_add.p[0] = current_points[0];
                    points_to_add.p[1] = current_points[0];
                    points_to_add.p[2] = current_points[1];
                    points_to_add.p[3] = current_points[1];
                    break;
                }
            case SkPath::kQuad_Verb:
                {
                    // iter.next returns 3 points
                    points_to_add.p[0] = current_points[0];
                    points_to_add.p[1] = current_points[1];
                    points_to_add.p[2] = current_points[2];
                    points_to_add.p[3] = current_points[2];
                    break;
                }
            case SkPath::kCubic_Verb:
                {
                    // iter.next returns 4 points
                    points_to_add.p[0] = current_points[0];
                    points_to_add.p[1] = current_points[1];
                    points_to_add.p[2] = current_points[2];
                    points_to_add.p[3] = current_points[3];
                    break;
                }
            case SkPath::kClose_Verb:
                {
                    // iter.next returns 1 point (the last point)
                    paths->push_back(CubicPath());
                    current_path = &paths->back();
                    continue;
                }
            case SkPath::kDone_Verb: // iter.next returns 0 points
            default:
                {
                    current_path = NULL;
                    // Will return false.
                    break;
                }
            }
            SkASSERT(current_path);
            if(!current_path)
            {
                paths->clear();
                return false;
            }
            current_path->push_back(points_to_add);
        }
        return true;
    }

    // static
    void PlatformDevice::LoadClippingRegionToDC(HDC context,
        const SkRegion& region,
        const SkMatrix& transformation)
    {
        HRGN hrgn;
        if(region.isEmpty())
        {
            // region can be empty, in which case everything will be clipped.
            hrgn = CreateRectRgn(0, 0, 0, 0);
        }
        else if(region.isRect())
        {
            // We don't apply transformation, because the translation is already applied
            // to the region.
            hrgn = CreateRectRgnIndirect(&SkIRectToRECT(region.getBounds()));
        }
        else
        {
            // It is complex.
            SkPath path;
            region.getBoundaryPath(&path);
            // Clip. Note that windows clipping regions are not affected by the
            // transform so apply it manually.
            // Since the transform is given as the original translation of canvas, we
            // should apply it in reverse.
            SkMatrix t(transformation);
            t.setTranslateX(-t.getTranslateX());
            t.setTranslateY(-t.getTranslateY());
            path.transform(t);
            LoadPathToDC(context, path);
            hrgn = PathToRegion(context);
        }
        int result = SelectClipRgn(context, hrgn);
        SkASSERT(result != ERROR);
        result = DeleteObject(hrgn);
        SkASSERT(result != 0);
    }

} //namespace skia